import { StateContext } from '@ngxs/store';
import { elvis } from './util';
import { StoreBase } from './state.base';
import { PagedResultDto } from '@abp/ng.core';
import { concat } from 'rxjs';
import { Type } from '@angular/core';

export interface DocumentListStateModel<T extends { id: string; documentId: string }> {
  models: T[];
  totalCount: number;
  selectedModels: T[];
}

export function defaultDocumentListState(): DocumentListStateModel<any> {
  return {
    models: [],
    totalCount: 0,
    selectedModels: [],
  };
}

export type ExtendsDocumentListStore<T extends { id: string; documentId: string }> = Type<
  DocumentListStore<T>
>;

export abstract class DocumentListStore<
  T extends { id: string; documentId: string }
> extends StoreBase<DocumentListStore<T>> {
  public static get getModels() {
    return state => {
      return elvis(state, this.staticStorePath).models || [];
    };
  }

  public static get getTotalCount() {
    return state => {
      return elvis(state, this.staticStorePath).totalCount || 0;
    };
  }

  public static get getSelectedModels() {
    return state => {
      return elvis(state, this.staticStorePath).selectedModels || [];
    };
  }

  public static get getIsAllSelected() {
    return state => {
      const { models, selectedModels } = elvis(state, this.staticStorePath);
      return (
        models.every(model => selectedModels.some(item => item.id === model.id)) && !!models?.length
      );
    };
  }

  public static get getIndeterminate() {
    return state => {
      const { models, selectedModels } = elvis(state, this.staticStorePath);
      return (
        models.some(model => selectedModels.some(item => item.id == model.id)) &&
        !models.every(model => selectedModels.some(item => item.id == model.id))
      );
    };
  }

  public static get getSelectedCount() {
    return state => {
      const { selectedModels } = elvis(state, this.staticStorePath);
      return selectedModels?.length ?? 0;
    };
  }

  constructor(storeClass: ExtendsDocumentListStore<T>) {
    super(storeClass);
    this.setup(
      storeClass,
      'getModels',
      'targetModelSelect',
      'targetAllSelect',
      'clearSelect',
      'delete'
    );
  }

  public getModels(
    { patchState, getState }: StateContext<DocumentListStateModel<T>>,
    { payload }: any
  ) {
    const { selectedModels } = getState();
    return this.onGetModels(payload).then(result => {
      patchState({
        models: result.items,
        totalCount: result.totalCount,
        selectedModels,
      });
    });
  }

  public targetModelSelect(
    { patchState, getState }: StateContext<DocumentListStateModel<T>>,
    { payload }: any
  ) {
    let { selectedModels } = getState();
    if (!selectedModels.some(item => item.id === payload.id))
      selectedModels = [...selectedModels, payload];
    else selectedModels = [...selectedModels.filter(item => item.id !== payload.id)];

    patchState({ selectedModels });
  }

  public targetAllSelect({ patchState, getState }: StateContext<DocumentListStateModel<T>>) {
    let { models, selectedModels } = getState();
    const allselected = models.every(model => selectedModels.some(item => item.id === model.id));

    if (allselected) {
      selectedModels = [
        ...selectedModels.filter(item => !models.some(model => model.id === item.id)),
      ];
    } else {
      selectedModels = [
        ...selectedModels,
        ...models.filter(model => !selectedModels.some(item => model.id === item.id)),
      ];
    }

    patchState({ selectedModels });
  }

  public clearSelect({ patchState }: StateContext<DocumentListStateModel<T>>) {
    patchState({ selectedModels: [] });
  }

  public delete({ patchState, getState }: StateContext<DocumentListStateModel<T>>) {
    const { selectedModels } = getState();
    const ids = selectedModels
      .map(x => x.documentId)
      .filter((item, index, arr) => arr.indexOf(item) === index);
    return concat(
      ...ids.map(id =>
        this.onDelete(id).then(() => {
          let { models, selectedModels } = getState();
          if (selectedModels.some(item => item.documentId === id))
            selectedModels = [...selectedModels.filter(item => item.documentId !== id)];
          if (models.some(item => item.documentId === id))
            models = [...models.filter(item => item.documentId !== id)];
          patchState({
            models,
            selectedModels,
          });
        })
      )
    );
  }

  protected abstract onGetModels(payload: any): Promise<PagedResultDto<T>>;
  protected abstract onDelete(payload: string): Promise<void>;
}
